跳到主要内容

Redis7 (二) 基本数据类型之String

参考视频教程:Redis零基础到进阶,最强redis7教程 官网:Redis 参考命令:Redis 中 String(字符串)类型的命令 参考书籍:Redis设计与实现-黄健宏-微信读书 我们都知道 Redis 提供了丰富的数据类型,常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)

一 基本认识

1.1 概述

  • String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配
  • 内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M
  • String 是最基本的 key-value 结构,key 是唯一标识,value 是具体的值,value其实不仅是字符串, 也可以是数字(整数或浮点数),value 最多可以容纳的数据长度是 512M
  1. String 是 Redis 最基本的类型,一个key对应一个value
  2. String类型是二进制安全的。意思是 redis 的String可以包含任何数据。比如jpg图片或者序列化对象
  3. 二进制安全是指,如果在传输数据时,保证二进制数据的信息安全,也就是不被篡改、破译,如果被攻击,能够及时检测出来

二进制安全特点:

  1. 编码、解码发生在客户端,执行效率高;
  2. 不需要频繁的编解码,不会出现乱码;

1.2 常用指令

SET和GET命令

信息

选项

从2.6.12版本开始,redis为SET命令增加了一系列选项:

  • EX seconds – 设置键key的过期时间,单位时秒
  • PX milliseconds – 设置键key的过期时间,单位时毫秒
  • NX – 只有键key不存在的时候才会设置key的值
  • XX – 只有键key存在的时候才会设置key的值
  • KEEPTTL -- 获取 key 的过期时间
  • GET -- 返回 key 存储的值,如果 key 不存在返回空
    信息

    返回值

    字符串: 如果SET命令正常执行那么回返回OK 多行字符串: 使用 GET 选项,返回 key 存储的值,如果 key 不存在返回空 : 否则如果加了NX 或者 XX选项,SET 没执行,那么会返回nil。
    提示

    案例

# 设置 key-value 类型的值
127.0.0.1:6379> set user admin
OK
# 获取一个key
127.0.0.1:6379> get user
"admin"
# 对已存在的值进行设置
127.0.0.1:6379> set user xiaomi
OK
# 会覆盖原来的值
127.0.0.1:6379> get user
"xiaomi"
# 设置一个过期的值 ex: 后面为秒
127.0.0.1:6379> set user aadmin ex 30
OK
# 查看过期时间
127.0.0.1:6379> ttl user
(integer) 25
# 是否存在key
127.0.0.1:6379> EXISTS user
(integer) 0
# 设置一个过期时间 px: 后面为毫秒
127.0.0.1:6379> set user admin px 10000
OK
127.0.0.1:6379> ttl user
(integer) 6
# 设置一个值,当 只有键key不存在的时候才会设置key的值
127.0.0.1:6379> setnx user admin
(integer) 1
127.0.0.1:6379> setnx user xiaomi
(integer) 0
127.0.0.1:6379> get user
"admin"
# 当只有键key存在的时候才会设置key的值
127.0.0.1:6379> set user xioami xx
OK
127.0.0.1:6379> get user
"xioami"
127.0.0.1:6379>
危险

Note: 下面这种设计模式并不推荐用来实现redis分布式锁。

批量设置

信息

Redis MSET 命令设置多个 key 的值为各自对应的 value

# 批量设置对个值 key-value
127.0.0.1:6379> mset user admin age 10 sex 1
OK
# 查看使用key
127.0.0.1:6379> keys *
1) "user"
2) "age"
3) "sex"
# 批量获取key
127.0.0.1:6379> mget age sex
1) "10"
2) "1"
127.0.0.1:6379>

计数器

信息
  • Redis INCR 命令将 key 中储存的数字值增一
  • Redis INCRBY 命令将 key 中储存的数字加上指定的增量值
  • DECR为键 key 储存的数字值减去一
  • DECRBY 将键 key 储存的整数值减去减量 decrement
# 增加
127.0.0.1:6379> incr age
(integer) 11
# 增量增加
127.0.0.1:6379> incrby age 2
(integer) 13
# 减少
127.0.0.1:6379> decr age
(integer) 12
# 增量减少
127.0.0.1:6379> decrby age 3
(integer) 9
127.0.0.1:6379>

过期时间

信息
  • SETEX 命令将键 key 的值设置为 value , 并将键 key 的生存时间设置为 seconds 秒钟
# 设置 key 在 30 秒后过期(该方法是针对已经存在的key设置过期时间)
# expire key seconds [NX|XX|GT|LT]
127.0.0.1:6379> EXPIRE name 30
(integer) 1

# 查看数据还有多久过期
127.0.0.1:6379> TTL name
(integer) 20
# 设置 key-value 类型的值,并设置该 key 的过期时间为 20 秒
# set key value [NX|XX] [GET] [EX seconds|PX milliseconds|EXAT uni
127.0.0.1:6379> SET name sid10t EX 20
OK
# setex key seconds value
127.0.0.1:6379> SETEX name 20 sid10t
OK

字符串常用方法

信息
  • Strlen 命令用于获取指定 key 所储存的字符串值的长度
  • Redis APPEND 命令用于为指定的 key 追加值
  • GETRANGE 命令返回存储在 key 中的字符串的子串,由 start 和 end 偏移决定(都包括在内)。
# 获取字符串的长度
127.0.0.1:6379> strlen age
(integer) 1
# 末尾追加值
127.0.0.1:6379> append user xioami
(integer) 11
127.0.0.1:6379> get user
"adminxioami"
# 范围截取
127.0.0.1:6379> getrange user 6 -1
"ioami"
127.0.0.1:6379>

1.3 基本数据结构

1.3.1 SDS的定义

每个sds.h/sdshdr结构表示一个SDS值

struct sdshdr {
//记录buf数组中已使用字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
};

image.png SDS 和我们认识的 C 字符串不太一样,之所以没有使用 C 语言的字符串表示,因为 SDS 相比于 C 的原生字符串:

  • SDS 获取字符串长度的时间复杂度是 O(1) 。因为 C 语言的字符串并不记录自身长度,所以获取长度的复杂度为 O(n);而 SDS 结构里用 len 属性记录了字符串长度,所以复杂度为 O(1)。

C 因为C字符串并不记录自身的长度信息,所以为了获取一个C字符串的长度,程序必须遍历整个字符串,对遇到的每个字符进行计数,直到遇到代表字符串结尾的空字符为止,这个操作的复杂度为O(N)。 image.png SDS 计算C字符串长度的过程和C字符串不同,因为SDS在len属性中记录了SDS本身的长度,所以获取一个SDS长度的复杂度仅为O(1)。 image.png

  • 杜绝缓冲区溢出: 除了获取字符串长度的复杂度高之外,C字符串不记录自身长度带来的另一个问题是容易造成缓冲区溢出(buffer overflow)

C 因为C字符串不记录自身的长度,所以strcat假定用户在执行这个函数时,已经为dest分配了足够多的内存,可以容纳src字符串中的所有内容,而一旦这个假定不成立时,就会产生缓冲区溢出。 举个例子,假设程序里有两个在内存中紧邻着的C字符串s1和s2,其中s1保存了字符串"Redis",而s2则保存了字符串MongoDB 如果一个程序员决定通过执行:将s1的内容修改为"Redis Cluster",但粗心的他却忘了在执行strcat之前为s1分配足够的空间,那么在strcat函数执行之后,s1的数据将溢出到s2所在的空间中,导致s2保存的内容被意外地修改 image.png SDS SDS的API里面也有一个用于执行拼接操作的sdscat函数,它可以将一个C字符串拼接到给定SDS所保存的字符串的后面,但是在执行拼接操作之前,sdscat会先检查给定SDS的空间是否足够,如果不够的话,sdscat就会先扩展SDS的空间,然后才执行拼接操作。 image.png

  • 二进制安全:C字符串中的字符必须符合某种编码(比如ASCII),并且除了字符串的末尾之外,字符串里面不能包含空字符,否则最先被程序读入的空字符将被误认为是字符串结尾,这些限制使得C字符串只能保存文本数据,而不能保存像图片、音频、视频、压缩文件这样的二进制数据,通过使用二进制安全的SDS,而不是C字符串,使得Redis不仅可以保存文本数据,还可以保存任意格式的二进制数据。

image.png

1.3.2 SDS API

image.png

1.3.3 字符串对象

信息

字符串对象的编码可以是int、raw或者embstr。

  • 如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成long),并将字符串对象的编码设置为int。
  • 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw。
  • 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值。

image.png 详细内容参考书籍

1.4 应用场景

1.4.1 计数器

因为 Redis 处理命令是单线程,所以执行命令的过程是原子的。因此 String 数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。

# 初始化文章的阅读量
127.0.0.1:6379[3]> SET page:10086 0
OK

#阅读量 +1
127.0.0.1:6379[3]> INCR page:10086
(integer) 1

#阅读量 +1
127.0.0.1:6379[3]> INCR page:10086
(integer) 2

# 获取对应文章的阅读量
127.0.0.1:6379[3]> GET page:10086
"2"

1.4.2 分布式锁

SET 命令有个 NX 参数可以实现「key 不存在才插入」,可以用它来实现分布式锁:

  • 如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
  • 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。

一般而言,还会对分布式锁加上过期时间,分布式锁的命令如下:

127.0.0.1:6379[3]> SET lock 10001 NX PX 10000
OK

1.4.3 缓存对象

一些业务场景下我们需要缓存一些信息:比如:用户的登录状态,验证码过期时间等等